import { Node, resources, Sprite, SpriteFrame, Asset, __private, JsonAsset, SpriteAtlas } from "cc";
import { GLoader, GObject } from "fairygui-cc";
import { ResCache } from "./ResCache";
import { ResCacheAtlas } from "./ResCacheAtlas";
import { ResCacheImage } from "./ResCacheImage";
import { ResCachePrefab } from "./ResCachePrefab";
import { ResCacheUI } from "./ResCacheUI";
import ResSetting from "./ResSetting";


export class Res {

    private static readonly inst: Res = new Res();

    private cacheList: Array<ResCache> = [];
    private cacheDict: { [key: string]: ResCache } = {};

    public constructor() {
        setInterval(this.update.bind(this), ResSetting.CheckInterval);
    }

    /**
     * 更新
     */
    private update() {
        let i = 0;
        let now = Date.now();
        let cacheList = this.cacheList;
        let cacheDict = this.cacheDict;
        while (i < cacheList.length) {
            let temp = cacheList[i];
            if (temp.check(now)) {
                cacheList.splice(i, 1);
                delete cacheDict[temp.path]
                temp.release();
            }
            else {
                ++i;
            }
        }
    }

    /**
     * 加载UI包资源
     * @param path 
     */
    private _loadPrefab(pkgName: string, callback: (cache: ResCachePrefab, node: Node) => void, target: any, autoDestroy?: boolean) {
        let cache = this.cacheDict[pkgName];
        if (!cache) {
            cache = new ResCachePrefab(pkgName);
            cache.setAutoDestroy(autoDestroy || false);
            this.cacheDict[pkgName] = cache;
            this.cacheList.push(cache);
        }
        cache.load(callback, target);
    }

    /**
     * 派发对象
     * @param path 
     * @param callback 
     * @param target 
     * @param args 
     */
    private _spawn(path: string, callback: Function, target?: any, ...args: any) {
        this._loadPrefab(path, (cache: ResCachePrefab, obj: Node) => {
            callback.call(target, obj, ...args);
        }, this, true);
    }

    /**
     * 清理精灵
     * @param target 
     */
    private _clearSprite(target: any) {
        let cache: ResCacheImage = target[ResSetting.Mark];
        if (cache) {
            cache.unSpawn();
            delete target[ResSetting.Mark];
        }

        if (target['spriteFrame']) {
            target.spriteFrame = null;
        }
        else if (target['texture']) {
            target.texture = null;
        }
    }

    /**
     * 设置精灵对象的图片
     * @param url 
     * @param callback 
     * @param target 
     */
    private _loadSpriteFrame(url: string | null, callback: (cache: ResCacheImage, image: SpriteFrame) => void, target: any, autoDestroy?: boolean) {
        if (url == null)
            return;
        let cache = this.cacheDict[url];
        if (!cache) {
            cache = new ResCacheImage(url);
            cache.setAutoDestroy(autoDestroy || false);
            this.cacheDict[url] = cache;
            this.cacheList.push(cache);
        }
        cache.load(callback, target);
    }

    /**
     * 设置精灵对象的图片
     * @param target 
     * @param url 
     */
    private _setSprite(target: any, url: string | null) {
        this._loadSpriteFrame(url, (cache: ResCacheImage, image: SpriteFrame) => {
            this._clearSprite(target);
            target[ResSetting.Mark] = cache;
            Res.fillSpriteFrame(target, image);
        }, this, true);
    }

    /**
     * 设置精灵对象的图片
     * @param url 
     * @param callback 
     * @param target 
     */
    private _loadSpriteAtlas(url: string | null, callback: Function, target: any) {
        if (url == null)
            return;
        let cache = this.cacheDict[url];
        if (!cache) {
            cache = new ResCacheAtlas(url);
            this.cacheDict[url] = cache;
            this.cacheList.push(cache);
        }
        cache.load(callback, target);
    }

    /**
     * 设置精灵对象的图片
     * @param target 
     * @param url 
     */
    private _setSpriteByAtlas(target: any, name: string, path: string) {
        this._clearSprite(target);
        this._loadSpriteAtlas(path, (cache: ResCacheAtlas) => {
            target[ResSetting.Mark] = cache;
            let atlas = cache.spawn() as SpriteAtlas;
            let image = atlas.getSpriteFrame(name);
            Res.fillSpriteFrame(target, image);
        }, this);
    }


    /**
     * 加载UI包资源
     * @param path 
     */
    private _loadUIPackage(pkgName: string, callback: Function, target: any, autoDestroy?: boolean) {
        let cache = this.cacheDict[pkgName];
        if (!cache) {
            cache = new ResCacheUI(pkgName);
            this.cacheDict[pkgName] = cache;
            cache.setAutoDestroy(autoDestroy || false);
            this.cacheList.push(cache);
        }
        cache.load(callback, target);
    }

    /**
     * 加载UI包资源
     * @param path 
     */
    private async _loadUIPackageAsync(pkgName: string, autoDestroy?: boolean): Promise<ResCacheUI | null> {
        return new Promise((resolve, reject) => {
            this._loadUIPackage(pkgName, (cache: ResCache) => {
                resolve(cache as ResCacheUI);
            }, this, autoDestroy);
        });
    }

    /**
     * 加载UI对象
     * @param path 
     */
    private _spawnUI(pkgName: string, resName: string, callback: (asset: GObject) => void, target: any) {
        this._loadUIPackage(pkgName, (cache: ResCacheUI) => {
            let obj = cache.spawn(resName);
            callback.call(target, obj);
        }, this);
    }

    /**
     * 异步加载UI对象
     * @param path 
     */
    private async _spawnUIAsync(pkgName: string, resName: string): Promise<GObject | null> {
        return new Promise((resolve, reject) => {
            this._spawnUI(pkgName, resName, (cache: GObject) => {
                resolve(cache);
            }, this);
        });
    }

    /**
     * 派发对象
     * @param path 
     * @param callback 
     * @param target 
     * @param args 
     */
    public static spawn(path: string, callback: Function, target?: any, ...args: any) {
        Res.inst._spawn(path, callback, target, ...args);
    }

    /**
     * 派发对象
     * @param path 
     * @param callback 
     * @param target 
     * @param args 
     */
    public static async spawnAsync(path: string): Promise<Node> {
        return new Promise<Node>((resolve, reject) => {
            Res.spawn(path, (node: Node) => {
                resolve(node);
            });
        });
    }

    /**
     * 回收资源
     * @param node 
     */
    public static recycle(node: any) {
        let cache = node[ResSetting.Mark];
        if (cache)
            cache.unSpawn(node);
        // else
        //     console.error(`资源已经被回收 ${node ? node.name : 'null'}`);
    }

    /**
     * 设置精灵对象的图片
     * @param url 
     * @param callback 
     * @param that 
     */
    public static loadSpriteAtlas(url: string | null, callback: Function, that: any) {
        Res.inst._loadSpriteAtlas(url, callback, that);
    }

    /**
     * 设置精灵对象的图片
     * @param target 
     * @param url 
     */
    public static async loadSpriteAtlasAsync(url: string | null): Promise<SpriteAtlas> {
        return new Promise<SpriteAtlas>((resolve, reject) => {
            Res.loadSpriteAtlas(url, (cache: ResCacheAtlas) => {
                let sf = cache.spawn();
                resolve(sf);
            }, this);
        });
    }

    /**
     * 设置精灵对象的图片
     * @param url 
     * @param callback 
     * @param that 
     */
    public static loadSpriteFrame(url: string | null, callback: (cache: ResCacheImage, image: SpriteFrame) => void, that: any, autoDestroy: boolean = true) {
        Res.inst._loadSpriteFrame(url, callback, that, autoDestroy);
    }

    /**
     * 设置精灵对象的图片
     * @param target 
     * @param url 
     */
    public static async loadSpriteFrameAsync(url: string | null, autoDestroy: boolean = true): Promise<SpriteFrame> {
        return new Promise<SpriteFrame>((resolve, reject) => {
            Res.loadSpriteFrame(url, (cache: ResCacheImage, image: SpriteFrame) => {
                resolve(image);
            }, this, autoDestroy);
        });
    }

    /**
     * 设置精灵对象的图片
     * @param target 
     * @param path 
     */
    public static setSprite(target: GLoader | Sprite, path: string | null) {
        Res.inst._setSprite(target, path);
    }

    /**
     * 设置精灵对象的图片通过图集
     * @param target 
     * @param name 精灵的名字
     * @param path 图集路径 
     */
    public static setSpriteByAtlas(target: GLoader | Sprite, name: string, path: string) {
        Res.inst._setSpriteByAtlas(target, name, path);
    }

    /**
     * 清理精灵
     * @param target 
     */
    public static clearSprite(target: any) {
        Res.inst._clearSprite(target);
    }

    /**
     * 加载资源
     * @param path 
     * @param type 
     * @returns 
     */
    public static load<T extends Asset>(path: string, type: __private._cocos_core_asset_manager_shared__AssetType<T>): Promise<T | null> {
        return new Promise<T | null>((resolve, reject) => {
            resources.load(path, type, (err: any, asset: T) => {
                if (asset) {
                    resolve(asset);
                }
                else {
                    console.error(err);
                    resolve(null);
                }
            });
        });
    }

    /**
     * 加载远程图片
     * @param url 
     * @returns 
     */
    public static loadJson(url: string): Promise<JsonAsset | null> {
        return new Promise<JsonAsset | null>((resolve, reject) => {
            resources.load<JsonAsset>(url, (err, asset: JsonAsset) => {
                resolve(asset);
            });
        });
    }

    /**
     * 给对象填充精灵
     * @param target 
     * @param spriteFrame 
     */
    public static fillSpriteFrame(target: any, spriteFrame: SpriteFrame | null) {
        if (!spriteFrame) return;
        if (target instanceof Sprite) {
            target.spriteFrame = spriteFrame;
        }
        else if (target instanceof GLoader) {
            target.texture = spriteFrame;
        }
        else {
            console.error("错误对象不能赋值：SpriteFrame");
            console.error(target);
        }
    }

    /**
     * 加载UI包资源
     * @param path 
     */
    public static loadUIPackage(pkgName: string, callback: Function, target: any, autoDestroy?: boolean) {
        Res.inst._loadUIPackage(pkgName, callback, target, autoDestroy);
    }

    /**
     * 加载UI包资源
     * @param path 
     */
    public static async loadUIPackageAsync(pkgName: string, autoDestroy?: boolean): Promise<ResCacheUI | null> {
        return await Res.inst._loadUIPackageAsync(pkgName, autoDestroy);
    }

    /**
     * 加载UI包资源
     * @param path 
     */
    public static spawnUI(pkgName: string, resName: string, callback: (asset: GObject) => void, target: any) {
        Res.inst._spawnUI(pkgName, resName, callback, target);
    }

    /**
     * 加载UI包资源
     * @param path 
     */
    public static async spawnUIAsync(pkgName: string, resName: string): Promise<GObject | null> {
        return await Res.inst._spawnUIAsync(pkgName, resName);
    }
}